// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
// Copyright (c) 2016 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#if WITH_LEGACY_PC_CONSOLE

#include <arch/x86.h>
#include <lib/io.h>
#include <platform/console.h>
#include <platform/pc.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* memory mapped framebuffer */
#define FB (0xB8000U + KERNEL_ASPACE_BASE)

/* CGA values */
#define CURSOR_START 0x0A
#define CURSOR_END 0x0B
#define VIDEO_ADDRESS_MSB 0x0C
#define VIDEO_ADDRESS_LSB 0x0D
#define CURSOR_POS_MSB 0x0E
#define CURSOR_POS_LSB 0x0F

/* curr settings */
static unsigned char curr_x;
static unsigned char curr_y;
static unsigned char curr_start;
static unsigned char curr_end;
static unsigned char curr_attr = 0x7;

/* video page buffer */
#define VPAGE_SIZE 2048
#define PAGE_MAX 8

static int active_page = 0;
static int visual_page = 0;

static int curs_x[PAGE_MAX];
static int curs_y[PAGE_MAX];

static struct {
    int x1, y1, x2, y2;
} view_window = {
    0, 0, 79, 24};

void platform_init_console(void) {
    curr_save();
    window(0, 0, 79, 24);
    clear();
    place(0, 0);
}

void set_visual_page(int page) {
    unsigned short page_offset = page * VPAGE_SIZE;
    visual_page = page;

    outp(CGA_INDEX_REG, VIDEO_ADDRESS_LSB);
    outp(CGA_DATA_REG, page_offset & 0xFF);
    outp(CGA_INDEX_REG, VIDEO_ADDRESS_MSB);
    outp(CGA_DATA_REG, (page_offset >> 8) & 0xFF);
}

void set_active_page(int page) {
    curs_x[active_page] = curr_x;
    curs_y[active_page] = curr_y;
    curr_x = curs_x[page];
    curr_y = curs_y[page];
    active_page = page;
}

int get_visual_page(void) {
    return visual_page;
}

int get_active_page(void) {
    return active_page;
}

void place(int x, int y) {
    unsigned short cursor_word = x + y * 80 + active_page * VPAGE_SIZE;

    /*
     * program CGA using index reg, then data reg
     */
    outp(CGA_INDEX_REG, CURSOR_POS_LSB);
    outp(CGA_DATA_REG, cursor_word & 0xFF);
    outp(CGA_INDEX_REG, CURSOR_POS_MSB);
    outp(CGA_DATA_REG, (cursor_word >> 8) & 0xFF);

    curr_x = x;
    curr_y = y;
}

void cursor(int start, int end) {
    outp(CGA_INDEX_REG, CURSOR_START);
    outp(CGA_DATA_REG, start);
    outp(CGA_INDEX_REG, CURSOR_END);
    outp(CGA_DATA_REG, end);
}

void curr_save(void) {
#if 0
    /* grab some info from the bios data area (these should be defined in memmap.h */
    curr_attr = *((unsigned char *)FB + 159);
    curr_x = *((unsigned char *)0x00450);
    curr_y = *((unsigned char *)0x00451);
    curr_end = *((unsigned char *)0x00460);
    curr_start = *((unsigned char *)0x00461);
#endif
    active_page = visual_page = 0;
}

void curr_restore(void) {
#if 0
    *((unsigned char *)0x00450) = curr_x;
    *((unsigned char *)0x00451) = curr_y;
#endif

    place(curr_x, curr_y);
    cursor(curr_start, curr_end);
}

void window(int x1, int y1, int x2, int y2) {
    view_window.x1 = x1;
    view_window.y1 = y1;
    view_window.x2 = x2;
    view_window.y2 = y2;

    //place(x1, y1);
}

void _clear(char c, char attr, int x1, int y1, int x2, int y2) {
    register int i, j;
    unsigned short w = attr;

    w <<= 8;
    w |= c;
    for (i = x1; i <= x2; i++) {
        for (j = y1; j <= y2; j++) {
            *((unsigned short*)(uintptr_t)(FB + 2 * i + 160 * j + 2 * active_page * VPAGE_SIZE)) = w;
        }
    }

    place(x1, y1);
    curr_y = y1;
    curr_x = x1;
}

void clear() {
    _clear(' ', curr_attr, view_window.x1, view_window.y1, view_window.x2,
           view_window.y2);
}

void _scroll(char attr, int x1, int y1, int x2, int y2) {
    register int x, y;
    unsigned short xattr = attr << 8, w;
    unsigned char* v = (unsigned char*)(uintptr_t)(FB + active_page * (2 * VPAGE_SIZE));

    for (y = y1 + 1; y <= y2; y++) {
        for (x = x1; x <= x2; x++) {
            w = *((unsigned short*)(v + 2 * (y * 80 + x)));
            *((unsigned short*)(v + 2 * ((y - 1) * 80 + x))) = w;
        }
    }

    for (x = x1; x <= x2; x++) {
        *((unsigned short*)(v + 2 * ((y - 1) * 80 + x))) = xattr;
    }
}

void scroll(void) {
    _scroll(curr_attr, view_window.x1, view_window.y1, view_window.x2,
            view_window.y2);
}

void cputc(char c) {
    static unsigned short scan_x, x, y;
    unsigned char* v = (unsigned char*)(uintptr_t)(FB + active_page * (2 * VPAGE_SIZE));
    x = curr_x;
    y = curr_y;

    switch (c) {
    case '\t':
        x += 8;
        if (x >= view_window.x2 + 1) {
            x = view_window.x1;
            if (y == view_window.y2) {
                scroll();
            } else {
                y++;
            }
        } else {
            scan_x = 0;

            while ((scan_x + 8) < x) {
                scan_x += 8;
            }

            x = scan_x;
        }
        break;

    case '\r':
        x = view_window.x1;
        break;

    case '\n':
        if (y == view_window.y2) {
            scroll();
        } else {
            y++;
        }
        break;

    case '\b':
        x--;
        break;

    default:
        *(v + 2 * (x + y * 80)) = c;
        x++;

        if (x >= view_window.x2 + 1) {
            x = view_window.x1;
            if (y == view_window.y2) {
                scroll();
            } else {
                y++;
            }
        }
    }

    place(x, y);
}

void cputs(char* s) {
    char c;
    while (*s != '\0') {
        c = *s++;
        cputc(c);
    }
}

void puts_xy(int x, int y, char attr, char* s) {
    unsigned char* v = (unsigned char*)(uintptr_t)(FB + (80 * y + x) * 2 + active_page * (2 * VPAGE_SIZE));
    while (*s != 0) {
        *v = *s;
        s++;
        v++;
        *v = attr;
        v++;
    }
}

void putc_xy(int x, int y, char attr, char c) {
    unsigned char* v = (unsigned char*)(uintptr_t)(FB + (80 * y + x) * 2 + active_page * (2 * VPAGE_SIZE));
    *v = c;
    v++;
    *v = attr;
}

int printf_xy(int x, int y, char attr, char* fmt, ...) {
    char cbuf[200];
    va_list parms;
    int result;

    va_start(parms, fmt);
    result = vsnprintf(cbuf, sizeof(cbuf), fmt, parms);
    va_end(parms);

    puts_xy(x, y, attr, cbuf);

    return result;
}

#endif
